<?php
// +----------------------------------------------------------------------
// | Bwsaas
// +----------------------------------------------------------------------
// | Copyright (c) 2015~2020 http://www.buwangyun.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Gitee ( https://gitee.com/buwangyun/bwsaas )
// +----------------------------------------------------------------------
// | Author: buwangyun <hnlg666@163.com>
// +----------------------------------------------------------------------
// | Date: 2021-04-27 12:55:00
// +----------------------------------------------------------------------

namespace buwang\base;

use app\manage\model\Token;
use buwang\exception\AuthException;
use buwang\service\AuthService;
use think\facade\Config;
use think\facade\View;
use buwang\service\PluginService;

/**
 * 控制器基础类
 */
abstract class PluginBaseController extends Manage
{
    //无需登录的方法,同时也就不需要鉴权了
    protected $noNeedLogin = ['*'];
    //无需鉴权的方法,但需要登录
    protected $noNeedRight = ['*'];
    //权限控制类
    protected $auth_service = null;
    // 插件名
    protected $addon = '';
    //插件路径
    protected $controller = null;
    //插件执行方法
    protected $action = null;
    // 插件路径
    protected $addon_path;
    // 视图模型实例
    protected $view;
    // 插件配置
    private $addon_config;
    // 插件信息
    private $addon_info;
    //布局模板 可用相对地址 如 ../../../view/public/layout
    protected $layout = 'layout';

    public function __construct()
    {
        if(!$this->app) $this->app = app();
        if(!$this->request) $this->request = $this->app->request;
        //移除HTML标签
        $this->request->filter('trim');
        // 是否自动转换控制器和操作名
        $convert = Config::get('url_convert');
        $filter = $convert ? 'strtolower' : 'trim';
        // 处理路由参数
        //$var = $this->>app->request->rule()->getVars();
        $var = $this->request->param(['addon', 'controller', 'action']);
        $addon = isset($var['addon']) ? $var['addon'] : '';
        $controller = isset($var['controller']) ? $var['controller'] : '';
        $action = isset($var['action']) ? $var['action'] : '';

        //当前插件名，方法，控制器
        $this->addon = $addon ? call_user_func($filter, $addon) : '';
        $this->controller = $controller ? call_user_func($filter, $controller) : 'index';
        $this->action = $action ? call_user_func($filter, $action) : 'index';

        $this->addon_path = $this->app->addons->getAddonsPath() . $this->addon . DIRECTORY_SEPARATOR;
        $this->addon_config = "addon_{$this->addon}_config";
        $this->addon_info = "addon_{$this->addon}_info";
        //view模板，layout模板布局设置
        $this->view = clone VIEW::engine('Think');
        $view_data = Config::get('view');
        $view_data['tpl_replace_string']['__ADDON__'] = '/static/addons/' . $this->addon . '/';
        $this->view->config($view_data);
        // 如果有使用模板布局
        //$this->layout && $this->>app->view->engine()->layout($this->layout);
        //自动加载插件目录下的composer包
        if (empty(self::$vendorLoaded[$this->addon])) {
            $pluginVendorAutoLoadFile = $this->addon_path .'vendor'.DS.'autoload.php';
            if (file_exists($pluginVendorAutoLoadFile)) {
                require_once $pluginVendorAutoLoadFile;
            }
            self::$vendorLoaded[$this->addon] = true;
        }

        $thisModule = "addons/{$this->addon}";
        $thisController = $this->controller;
        $thisAction = $this->action;
        list($thisControllerArr, $jsPath) = [explode('.', $thisController), null];
        foreach ($thisControllerArr as $vo) {
            empty($jsPath) ? $jsPath = parse_name($vo) : $jsPath .= '/' . parse_name($vo);
        }
        $autoloadJs = file_exists(root_path('public') . "static/{$thisModule}/js/{$jsPath}.js") ? true : false;
        $thisControllerJsPath = "{$thisModule}/js/{$jsPath}.js";
        $data = [
            'adminModuleName' => $thisModule,//插件请求基地址
            'thisController' => parse_name($thisController),
            'thisAction' => $thisAction,
            'thisRequest' => parse_name("{$thisModule}/{$thisController}/{$thisAction}"),
            'thisControllerJsPath' => "{$thisControllerJsPath}",
            'autoloadJs' => $autoloadJs,
            'isSuperAdmin' => 0,
            'domain' =>  $this->request->domain(),
            'version' => env('app_debug') ? time() : BW_VERSION,
        ];
        $this->assign($data);
        $this->pluginAuth();
        parent::initialize();
        ///父类的调用必须放在设置模板路径之后
        parent::__construct();

    }

    //插件必备初始化函数 插件权限节点鉴权
    private function pluginAuth()
    {
        //转化获取当前的路由节点地址
        $node = "/addons/{$this->addon}/" . parse_name("{$this->controller}", 1) . "/{$this->action}";
        $this->auth_service = AuthService::instance();
        //传入当前请求的方法
        $this->auth_service->setAction($this->action);
        //TODO:2021-7-9 15:25:46 判断插件是否正常安装启用
        //插件文件完整性校验
        PluginService::check($this->addon);
        //插件安装检测
        if(!PluginService::checkInstall($this->addon)) throw new AuthException("{$this->addon}插件未正常开启，请检查插件是否安装或启用。");

        //检测是否登录
        $this->scopes = '';
        $this->token = get_token($this->request);
        $code = 401;
        $err_msg = "未登录，请先去登录";
        $err_code = 400000;
        $this->user = false;
        if ($this->token) {
            //不需要登录捕获422错误
            //获取到token,解析token,捕获token签名不正确，签名尚未生效,签名在某个时间点之后才能用，token已经过期，token解析错误
            try{
                $jwtinfo = self::decodeToken($this->token);
            }catch (\Throwable $e){
                $this->token = '';//token解析报错算token失效,当做未登录处理
                $jwtinfo = null;
            }
            if($jwtinfo){
                //和redis存储的token进行验证有效性
                $this->user = Token::validateRedisToken($this->token, $jwtinfo);
                $code = Token::getErrorCode() == 400001 ? 401 : 200;
                $err_code = Token::getErrorCode() ?: 400000;
                $err_msg = Token::getError("未登录，请先去登录");
            }
        }
        //赋值用户的登录状态
        if($this->user) $this->isUserLogin =true;
        // 检测是否需要验证登录
        if (!$this->auth_service->match($this->noNeedLogin)) {
            if (!$this->token || !$this->user) throw new AuthException($err_msg,401);
            // 判断是否需要验证权限
            if (!$this->auth_service->match($this->noNeedRight)) {
                // 判断控制器和方法判断是否有对应权限
                //目前只鉴权scopes为admin和member的后台相关节点
                AuthService::auth($this->user['id'], $this->scopes, $node);
            }
        }
    }

    /**
     * 加载模板输出
     * @param string $template
     * @param array $vars 模板文件名
     * @return false|mixed|string   模板输出变量
     */
    protected function fetch($template = '', $vars = [])
    {
        return $this->view->fetch($template, $vars);
    }

    /**
     * 渲染内容输出
     * @access protected
     * @param string $content 模板内容
     * @param array $vars 模板输出变量
     * @return mixed
     */
    protected function display($content = '', $vars = [])
    {
        return $this->view->display($content, $vars);
    }

    /**
     * 模板变量赋值
     * @access protected
     * @param mixed $name 要显示的模板变量
     * @param mixed $value 变量的值
     * @return $this
     */
    protected function assign($name, $value = '')
    {
        if (is_array($name)) {
            $this->view->assign($name);
        } else {
            $this->view->assign([$name => $value]);
        }

        return $this;
    }

    /**
     * 初始化模板引擎
     * @access protected
     * @param array|string $engine 引擎参数
     * @return $this
     */
    protected function engine($engine)
    {
        $this->view->engine($engine);

        return $this;
    }

    /**
     * 插件基础信息
     * @return array
     */
    final public function getInfo()
    {
        $info = Config::get($this->addon_info, []);
        if ($info) {
            return $info;
        }
        // 文件属性
        $info = $this->info ?? [];
        // 文件配置
        $info_file = $this->addon_path . 'info.ini';
        if (is_file($info_file)) {
            $_info = parse_ini_file($info_file, true, INI_SCANNER_TYPED) ?: [];
            $_info['url'] = addons_url();
            $info = array_merge($_info, $info);
        }
        Config::set($info, $this->addon_info);

        return isset($info) ? $info : [];
    }

    /**
     * 获取配置信息
     * @param bool $type 是否获取完整配置
     * @return array|mixed
     */
    final public function getConfig($type = false)
    {
        $config = Config::get($this->addon_config, []);
        if ($config) {
            return $config;
        }
        $config_file = $this->addon_path . 'config.php';
        if (is_file($config_file)) {
            $temp_arr = (array)include $config_file;
            if ($type) {
                return $temp_arr;
            }
            foreach ($temp_arr as $key => $value) {
                $config[$key] = $value['value'];
            }
            unset($temp_arr);
        }
        Config::set($config, $this->addon_config);

        return $config;
    }
    /**
     * 获取对象的属性字段及属性值
     * @param  [type]  $property_scope  属性域
     * @param  boolean $static_excluded 是否排除静态属性
     * @return array
     * @throws \ReflectionException|\Exception
     */
    protected function getProperties($property_scope = null, $static_excluded = false)
    {
        // 校验反射域是否合法
        if (isset($property_scope) && !in_array($property_scope, [
                \ReflectionProperty::IS_STATIC,
                \ReflectionProperty::IS_PUBLIC,
                \ReflectionProperty::IS_PROTECTED,
                \ReflectionProperty::IS_PRIVATE,
            ])) {
            throw new \Exception("reflection class property scope illegal!");
        }
        $properties_mapping = [];
        // 谈判官
        $classRef           = new \ReflectionClass($this);
        $properties         = isset($property_scope) ? $classRef->getProperties($property_scope) : $classRef->getProperties();
        foreach ($properties as $property) {
            // 为了兼容反射私有属性
            $property->setAccessible(true);
            // 当不想获取静态属性时
            if ($property->isStatic() && $static_excluded) {
                continue;
            }
            if(in_array($property->getName(),['noNeedLogin', 'noNeedRight'])){
                // 将得到的类属性同具体的实例绑定解析，获得实例上的属性值
                $properties_mapping[$property->getName()] = $property->getValue($this);
            }
        }
        return $properties_mapping;
    }

}
